dojo.provide("xray.Data");

dojo.declare(
	"xray.Data",
	null,
	{
		constructor:function(socket){
			this.socket = socket;
			dojo.connect(socket, "handleData", dojo.hitch(this, "add"));
			// This contains all the incoming data, stored in historical order.
			// There might be gaps in timestamps, since we dont get data every
			// millisecond, and we squeeze in data that are coming asynchronously!
			// So don't rely on this list staying the same for "old" data.
			this.data = [];
			// Maps the timestamp to a reference of the data in this.data
			// { 1234234523:<ref to this.data with this timestamp>, }
			this.timestampToDataMap = {};
		},
		
		add:function(){
			try {
				var rawData = this.socket.getData();
//console.log("rawData:", rawData);
			}catch(e){
				return;
			}
			
			try{
				var newData = dojo.fromJson("[" + rawData.substr(0, rawData.length-1) + "]");
			}catch(e){
//console.log(e, "rawData: ", rawData);
				return;
			}
			
//console.log("newData: ", dojo.toJson(newData));
			// Store the timestamps of the data in here where either memory, cpu or
			// any of the essential data is missing, that we can add by interpolating the
			// surrounding data.
			var dataMissing = [];
			// Store the oldest updated timestamp in here, so we can publish the
			// topic "xray/data/new" with this as parameter later.
			var oldestTimestamp = newData.length ? newData[0].timestamp : Infinity;
			for (var x=0, l=newData.length; x<l; x++){
				if (typeof newData[x]=="undefined"){
					continue;
				}
				
				// handle browser detection
				if (!this.currentClient){
					this.currentClient = newData[x].client;
					//dojo.publish("xray/data/browser", [this.currentClient]); sorry, i dont understand this currently ... I would rather then send an event when a new browser comes along or if a browser was turned off (mem=0, cpu=0)
				}else if (this.currentClient != newData[x].client){
					this.currentClient = newData[x].client;
					//dojo.publish("xray/data/browser", [this.currentClient]);
				}
				
				// publish data type event for listeners who want to handle specific data types
				// Is this heavy on performance?
				dojo.publish("xray/data/item/"+newData[x].log.type, [dojo.clone(newData[x])]);
//console.log("xray/data/item/"+newData[x].log.type);

				var t = newData[x].timestamp;
				var d = this.data;
				oldestTimestamp = t < oldestTimestamp ? t : oldestTimestamp;
				
				if (this.timestampToDataMap[t]){
					// Update the data.
					dojo.mixin(this.timestampToDataMap[t], newData[x]);
				} else {
					// Add the new data in.
					if (!d.length || d[d.length-1].timestamp < t){
						d.push(newData[x]);
						this.timestampToDataMap[t] = d[d.length-1];
					} else {
						// Find the slot where the new data belong in, by timestamp.
						for (var i=d.length-1; d[i].timestamp>t && i>-1; i--){}
						i++;
//console.log("d.length", d.length, "sneak in at:", i, t);
//console.log("...", i-1, d[i-1] ? d[i-1].timestamp: " ende");
//console.log("...", i-2, d[i-2] ? d[i-2].timestamp: " ende");
						var dTemp = d.slice(0, i);
						d = dTemp.concat([newData[x]]).concat(d.slice(i))
						dTemp = null; // Free the memory.
						this.timestampToDataMap[t] = d[i];
					}
				}
				
				this.data = d; // We really have to write it back :-(. I thought d would be a reference .... mmmh
				d = null;
				var current = this.timestampToDataMap[t];
				if (typeof current.memory=="undefined" || typeof current.cpu=="undefined"){
					dataMissing.push(t);
				}
			}	
			
//console.log(dojo.toJson(this.data, true))
//dojo.forEach(this.data, function(item, index){console.log("data["+index+"] = ", item.timestamp);});
			// Go over all dataMissing data sets and enrich them with the missing
			// data.
			for (var i=0, len = dataMissing.length; i<len; i++){
				var t = dataMissing[i];
				var d = this.timestampToDataMap[t];
				if (typeof d.memory == "undefined") d.memory = this._interpolate("memory", t);
				if (typeof d.cpu == "undefined") d.cpu = this._interpolate("cpu", t);
				this.timestampToDataMap[t] = d;
//console.log("finally:", dojo.toJson(this.timestampToDataMap[t]));
			}
			// Publish the timestamp of the oldest data just received. Sometimes
			// we just get older data, since they are coming in asynchronously.
			// The consumer has to handle the asynch data problem.
			// Use .get(timestamp) to retreive the data and merge them.
			dojo.publish("xray/data/new", [oldestTimestamp]);
//console.log("xray.Data.add: ",dojo.toJson(d, true));
		},
		
		_interpolate:function(property, t){
			// Interpolate linearly by the timestamp.
			// >>> var data = new xray.Data(null);
			// >>> data._interpolate("mem", 2, {mem:1, timestamp:1}, {mem:3, timestamp:3})
			// 2
			// >>> var data = new xray.Data(null);
			// >>> data._interpolate("mem", 2, {mem:10, timestamp:1}, {mem:100, timestamp:10})
			// 20
			var index = dojo.indexOf(this.data, this.timestampToDataMap[t]);
			// Find the dataSet BEFORE the one with the timestamp t, that has property defined!
			for (var i=index-1; typeof this.data[i][property]=="undefined" && i>1; i--){}
			var before = i>-1 ? this.data[i] : null;
			// Find the dataSet AFTER the one with the timestamp t, that has property defined
			// and is for the same client/browser.
			var browser = this.data[i].client;
			for (var i=index+1, len=this.data.length;
				typeof this.data[i][property]=="undefined" && browser!=this.data[i].client && i<len;
				i++){}
			var after = (typeof this.data[i][property]=="undefined" ? null : this.data[i]);

			if (before==null) return after==null ? 0 : after[property];
			if (after==null) return before[property];
			var b = parseFloat(before[property]), a = parseFloat(after[property]);
			var bt = before.timestamp, at = after.timestamp;
			return b + (a - b) / (at-bt) * (t - bt);
		},
		
		getByClient:function(client, start){
			return this._get(start, client);
		},
		
		get:function(start){
			return this._get(start);
		},
		
		_get:function(/*int?*/start, /*String?*/client){
			// summary: Get all the data by the given parameters.
			// start: The timestamp (in ms) where to start serving data.
			//			If no end is given, return all the data until the end.
			// client: If given return only the data for this browser.
			if (!this.data[0]) return [];
			var ret;
			if (!start){
				ret  = this.data;
			} else {
				var firstTimestamp = this.data[0].timestamp;
				// Let's get the next smaller timestamp before start.
				for (var s = start; !this.timestampToDataMap[s] && s > firstTimestamp; s--){}
				var startAt = (s <= firstTimestamp) ? 0 : this.data.indexOf(this.timestampToDataMap[s]);
				ret = this.data.slice(startAt);
			}
			if (client){
				var retClient = [];
				for (var i=0, l=ret.length; i<l; i++){
					if (ret[i].client==client){
						retClient.push(ret[i]);
					}
				}
				ret = retClient;
			}
			dojo.publish("xray/dataRetreival", [dojo.clone(ret)]);
			
			return ret;
		}
	}
);
